module StochasticSimulation.GibsonBruckAlgorithm

open System
open StochasticSimulation.ChemicalReaction.ChemicalReaction
open StochasticSimulation.ChemicalReaction.ChemicalSpecies
open System.Collections.Generic
open StochasticSimulation.ChemicalReaction.ReactionGraph
open StochasticSimulation.ChemicalReaction.PriorityQueue

let readNextTime nt =
    match nt with
    | Normal(t) -> t
    | Infinite(old) -> old

let fromNextTime aOld aNew nextTime now=
    let transform = 
        match nextTime with
        | Normal(t)   -> (aOld/aNew)*( t - now )
        | Infinite(t) -> t/aNew
    now + transform

let generateNewReactionTime (rnd:Random) now propensity =
    let randomTime = Math.Log(1.0/rnd.NextDouble() ) / propensity
    now + randomTime

let union left right =  
    left::right |> Seq.distinct |> Seq.toList

let calculateStoredTime (rnd:Random) (aOld,aNew) time isSure now = 
    match (aNew,isSure) with
        | (0.0,_)  ->  Infinite( aOld * ( (readNextTime time) - now) )
        | (_,true) ->  Normal( generateNewReactionTime rnd now aNew )
        | (_,false) -> Normal( fromNextTime aOld aNew time now  )
    
let executeNextReaction (graph:Graph) (queue:PriorityQueue)  reactions (t:double) (rnd:Random) system =
    let (next,nextT) = queue.Next
    
    let affecteds = union next (graph.GetAffected next)
    
    let updatedSystem = Seq.toList( next.React(system) )
    
    let tNow = readNextTime nextT

    let propensities = affecteds |> List.map ( fun r -> (r.Propensity system, r.Propensity updatedSystem) )

    let sures = List.map (fun a -> a = next) affecteds

    let nextTimes = List.zip3 propensities (queue.Times affecteds) sures |> List.map ( fun (p,r,s) -> calculateStoredTime rnd p r s tNow )
    
    queue.Update( List.zip affecteds nextTimes )

    { tau = tNow ; UpdatedSystem = updatedSystem }